class ServerController < ApplicationController
  skip_before_filter :verify_authenticity_token
  rescue_from OpenID::Server::ProtocolError, :with => :render_openid_error
  rescue_from OpenID::Server::ProtocolError, :with => :render_openid_error
  before_filter :login_required, :except => [:index, :cancel, :seatbelt_config, :seatbelt_login_state]
  before_filter :ensure_valid_checkid_request, :except => [:index, :cancel, :seatbelt_config, :seatbelt_login_state]
  after_filter :clear_checkid_request, :only => [:cancel, :complete]
  helper_method :sreg_request, :ax_fetch_request, :ax_store_request
  # This is the server endpoint which handles all incoming OpenID requests.
  # Associate and CheckAuth requests are answered directly - functionality
  # therefor is provided by the ruby-openid gem. Handling of CheckId requests
  # dependents on the users login state (see handle_checkid_request).
  # Yadis requests return information about this endpoint.
  def index
    clear_checkid_request
    respond_to do |format|
      format.html do
        if openid_request.is_a?(OpenID::Server::CheckIDRequest)
          handle_checkid_request
        elsif openid_request
          handle_non_checkid_request
        else
          render :text => t(:this_is_openid_not_a_human_ressource)
        end
      end
      format.xrds
    end
  end

  # This action decides how to process the current request and serves as
  # dispatcher and re-entry in case the request could not be processed
  # directly (for instance if the user had to log in first).
  # When the user has already trusted the relying party, the request will
  # be answered based on the users release policy. If the request is immediate
  # (relying party wants no user interaction, used e.g. for ajax requests)
  # the request can only be answered if no further information (like simple
  # registration data) is requested. Otherwise the user will be redirected
  # to the decision page.
  def proceed
    identity = identifier(current_user)
    if @site = current_user.humble_site.find_by(:url=>checkid_request.trust_root)
      resp = checkid_request.answer(true, nil, identity)
      resp = add_sreg(resp, @site.sreg_properties, current_user.roles_name.join(",").to_s, current_user.authentication_token.to_s) if sreg_request
      resp = add_ax(resp, @site.ax_properties) if ax_fetch_request
      resp = add_pape(resp, auth_policies, auth_level, auth_time)
      render_response(resp)
    elsif checkid_request.immediate && (sreg_request || ax_store_request || ax_fetch_request)
      render_response(checkid_request.answer(false))
    elsif checkid_request.immediate
      render_response(checkid_request.answer(true, nil, identity))
    else
      redirect_to decide_path
    end
  end

  # Displays the decision page on that the user can confirm the request and
  # choose which data should be transfered to the relying party.
  def decide
    @site = current_user.humble_site.find_or_initialize_by(:url=>checkid_request.trust_root)
    if sreg_request || ax_store_request || ax_fetch_request
      @site.user_confirm = current_user.user_confirm.find(:user_confirm_id =>params[:user_confirm_id]) unless params[:user_confirm_id].blank?
       @site.user_confirm = current_user.user_confirm.first  if @site.user_confirm.blank?
    end
  end

  # This action is called by submitting the decision form, the information entered by
  # the user is used to answer the request. If the user decides to always trust the
  # relying party, a new site according to the release policies the will be created.
  def complete
    if params[:cancel]
      cancel
    else
      resp = checkid_request.answer(true, nil, identifier(current_user))
      if params[:always]
        @site = current_user.humble_site.find_or_create_by(:user_confirm_id => humble_site_accessible[:user_confirm_id], :url => humble_site_accessible[:url])
        @site.update(humble_site_accessible)
      elsif sreg_request || ax_fetch_request
        @site = current_user.humble_site.find_or_initialize_by(:user_confirm_id => humble_site_accessible[:user_confirm_id], :url => humble_site_accessible[:url])
        @site.attributes = humble_site_accessible
      elsif ax_store_request
        @site = current_user.humble_site.find_or_initialize_by(:user_confirm_id => humble_site_accessible[:user_confirm_id], :url => humble_site_accessible[:url])
        not_supported, not_accepted, accepted = [], [], []
        ax_store_request.data.each do |type_uri, values|
          if property = UserConfirm.attribute_name_for_type_uri(type_uri)
            store_attribute = humble_site_accessible[:ax_store][property.to_sym]
            if store_attribute && !store_attribute[:value].blank?
            @site.persona.update_attribute(property, values.first)
            accepted << type_uri
            else
            not_accepted << type_uri
            end
          else
          not_supported << type_uri
          end
        end
        ax_store_response = (accepted.count > 0) ? OpenID::AX::StoreResponse.new : OpenID::AX::StoreResponse.new(false, "None of the attributes were accepted.")
      resp.add_extension(ax_store_response)
      end
      resp = add_pape(resp, auth_policies, auth_level, auth_time)
      resp = add_sreg(resp, @site.sreg_properties, current_user.roles,current_user.authentication_token.to_s) if sreg_request && @site.sreg_properties
      resp = add_ax(resp, @site.ax_properties) if ax_fetch_request && @site.ax_properties
      render_response(resp)
    end
  end

  # Cancels the current OpenID request
  def cancel
    redirect_to checkid_request.cancel_url
  end

  protected

  # Decides how to process an incoming checkid request. If the user is
  # already logged in he will be forwarded to the proceed action. If
  # the user is not logged in and the request is immediate, the request
  # cannot be answered successfully. In case the user is not logged in,
  # the request will be stored and the user is asked to log in.
  def handle_checkid_request
    if allow_verification?
      save_checkid_request
      redirect_to proceed_path
    elsif openid_request.immediate
      render_response(openid_request.answer(false))
    else
      reset_session
      request = save_checkid_request
      session[:return_to] = proceed_path
      redirect_to( request.from_trusted_domain? ? new_user_session_path : safe_login_path )
    end
  end

  # Stores the current OpenID request.
  # Returns the HumbleOpenidRequest
  def save_checkid_request
    clear_checkid_request
    request = HumbleOpenidRequest.create!(:parameters => openid_params)
    session[:request_token] = request.token

    request
  end

  # Deletes the old request when a new one comes in.
  def clear_checkid_request
    unless session[:request_token].blank?
      HumbleOpenidRequest.destroy_all :token => session[:request_token]
      session[:request_token] = nil
    end
  end

  # Use this as before_filter for every CheckID request based action.
  # Loads the current openid request and cancels if none can be found.
  # The user has to log in, if he has not verified his ownership of
  # the identifier, yet.
  def ensure_valid_checkid_request
    self.openid_request = checkid_request
    if !openid_request.is_a?(OpenID::Server::CheckIDRequest)
      redirect_to root_path, :alert => t(:identity_verification_request_invalid)
    elsif !allow_verification?
      flash[:notice] = logged_in? && !pape_requirements_met?(auth_time) ?
        t(:service_provider_requires_reauthentication_last_login_too_long_ago) :
        t(:login_to_verify_identity)
      session[:return_to] = proceed_path
      redirect_to new_user_session_path
    end
  end

  # The user must be logged in, he must be the owner of the claimed identifier
  # and the PAPE requirements must be met if applicable.
  def allow_verification?
    logged_in? && correct_identifier? && pape_requirements_met?(auth_time)
  end

  # Is the user allowed to verify the claimed identifier? The user
  # must be logged in, so that we know his identifier or the identifier
  # has to be selected by the server (id_select).
  def correct_identifier?
    (openid_request.identity == identifier(current_user) || openid_request.id_select)
  end

  # Clears the stored request and answers
  def render_response(resp)
    clear_checkid_request
    render_openid_response(resp)
  end

  # Transforms the parameters from the form to valid AX response values
  def transform_ax_data(parameters)
    data = {}
    parameters.each_pair do |key, details|
      if details['value']
        data["type.#{key}"] = details['type']
        data["value.#{key}"] = details['value']
      end
    end
    data
  end

  # Renders the exception message as text output
  def render_openid_error(exception)
    error = case exception
    when OpenID::Server::MalformedTrustRoot then "Malformed trust root '#{exception.to_s}'"
    else exception.to_s
    end
    render :text => h("Invalid OpenID request: #{error}"), :status => 500
  end

  private

  # The NIST Assurance Level, see:
  # http://openid.net/specs/openid-provider-authentication-policy-extension-1_0-01.html#anchor12
  def auth_level
    if $humble_config['use_ssl']
      current_user.last_authenticated_with_yubikey? ? 3 : 2
    else
    0
    end
  end

  def auth_time
    current_user.last_authenticated_at
  end

  def auth_policies
    # current_user.last_authenticated_with_yubikey? ?
      # [OpenID::PAPE::AUTH_MULTI_FACTOR, OpenID::PAPE::AUTH_PHISHING_RESISTANT] :
    []
  end
  
  def  humble_site_accessible
    params.require(:humble_site).permit!
      # params.require(:humble_site).permit( :user_confirm_id, :url, :properties, :ax_fetch, {:sreg =>[:fullname,:email,:gender]}, :created_at, :updated_at)
  end
  
  # def site_accessible
    # params.require(:site).permit( :user_id, :url, :properties, :ax_fetch,  :created_at, :updated_at,{:sreg =>[:fullname,:email]} )
  # end

end
